Skip to main content

Understanding Multi-Tenancy: What It Actually Means in Practice

1. Introduction: What is Multi-Tenancy, Really?

I’ve always heard people talk about multi-tenancy, but for the longest time, it felt like one of those buzzwords that companies throw around—like "enterprise-grade" or "scalable architecture"—without actually explaining what it means.

So, I finally sat down to figure it out. What exactly is multi-tenancy, and why does it matter?

At its core, multi-tenancy (usually) refers to a single application that serves multiple customers (tenants) while keeping their data isolated.

Does Multi-Tenancy Always Refer to the Data?**

No! Multi-tenancy can apply to:

  1. The database (one DB per tenant, schema per tenant, or row-level)
  2. The application (one Go app handling all tenants, or separate instances per tenant)
  3. The infrastructure (separate compute resources, like per-tenant EC2 instances, containers, or Kubernetes pods)

Since most people usually refer to data multi tenancy, we will start by covering that first.


Data Multi-Tenancy

The Three Main Types of Data Multi-Tenancy**

There are multiple ways to implement multi-tenancy, each with different trade-offs:

Multi-Tenancy ApproachHow It WorksProsCons
Database-per-Tenant (Separate Databases)Each customer gets their own database within the same instance or across instances.✅ Strong isolation ✅ Easier data migration per tenant❌ Higher resource usage ❌ Harder to manage at scale
Schema-per-Tenant (Shared DB, Separate Schemas)One database, but each tenant has a dedicated schema (tenant1.users, tenant2.users).✅ Better isolation than row-level ✅ Scales better than per-db❌ Harder to manage schema changes ❌ More complex migrations
Row-Based Multi-Tenancy (Shared DB, Shared Schema)All tenants’ data is in the same tables, with tenant_id as a key.✅ Most scalable ✅ Simplest to manage at scale❌ Harder to enforce isolation ❌ Risk of data leaks

Each of these models has use cases where it makes the most sense.


A. Database-Per-Tenant: The Cleanest but Most Expensive Approach

This is the approach that seems most intuitive—each customer gets their own database.

How It Works

  • A single Go application serves multiple tenants.
  • When a customer logs in, the app dynamically selects the correct database connection:
    func getDBForTenant(tenantID string) (*sql.DB, error) {
    dsn := fmt.Sprintf("postgres://user:pass@rds-instance/%s", tenantID)
    return sql.Open("postgres", dsn)
    }
  • Each tenant has full isolation from others—no risk of cross-tenant data leaks.

Why Use This Approach?

Security & Isolation – No accidental data mix-ups.
Easier per-tenant scaling – High-usage customers can be moved to their own RDS instance.
Simple database operations – Backups, restores, and migrations can be done per customer.

Why It’s Not Always Ideal

Higher cost – Each database takes up resources, even for small customers.
Connection limits – A database-per-tenant model can hit DB connection pool limits.
Harder to manage at scale – Hundreds or thousands of databases become painful to maintain.

Best for:

  • High-security applications (e.g., healthcare, finance).
  • Large customers who need isolated data environments.
  • When strict data residency (e.g., per-country data laws) is required.

B. Schema-Per-Tenant: The Middle Ground

Instead of separate databases, each tenant has their own schema within the same database:

  • tenant1.users
  • tenant2.orders
  • tenant3.logs

How It Works in Go

When querying, you dynamically set the schema for each request:

func setSchemaForTenant(db *sql.DB, tenantID string) error {
_, err := db.Exec(fmt.Sprintf("SET search_path TO tenant_%s", tenantID))
return err
}

Why This Approach?

More scalable than per-database – One DB, many schemas.
Still provides isolation – Tenants don’t share tables.
Easier maintenance than per-database – Fewer connections, single backup strategy.

Challenges

Schema management is harder – Every migration has to update all schemas.
Less isolation than per-database – A misconfigured query could still affect another tenant.

Best for:

  • SaaS applications with many small tenants.
  • Medium security requirements where full database isolation isn’t needed.
  • When scaling beyond hundreds of tenants but still wanting some isolation.

C. Row-Based Multi-Tenancy: The Most Scalable but Risky Approach

Instead of separate databases or schemas, all tenants’ data is stored in shared tables. Each table includes a tenant_id column:

SELECT * FROM users WHERE tenant_id = 123;

How It Works in Go

Every query must include tenant filtering:

func getUsersForTenant(db *sql.DB, tenantID int) ([]User, error) {
query := "SELECT * FROM users WHERE tenant_id = $1"
rows, err := db.Query(query, tenantID)
return scanUsers(rows), err
}

Why This Approach?

Best performance & scalability – No need to manage thousands of schemas or databases.
Simplifies onboarding – No need to provision a new DB/schema per customer.
Efficient resource usage – Fewer connections, easier caching.

Challenges

Biggest risk of data leaks – One incorrect query (SELECT * FROM users without WHERE tenant_id = X) could expose all customer data.
More complex permissioning – Every API call must be scoped properly.
Harder to migrate tenants – Moving a single customer to their own DB is non-trivial.

Best for:

  • High-scale SaaS products with thousands of customers.
  • Low-security use cases where cross-tenant data leaks aren’t catastrophic.
  • Products where all tenants follow the same schema (no per-customer customizations).

Choosing the Right Data Multi-Tenancy Model**

ScenarioBest Multi-Tenancy Approach
High-security, compliance-heavy appDatabase-per-tenant
Mid-size SaaS with many customersSchema-per-tenant
Large-scale SaaS with thousands of usersRow-based multi-tenancy
Small startup, unsure about scaleStart with schema-per-tenant or per-db, migrate later

7. Conclusion

  • Multi-tenancy isn’t one-size-fits-all—it’s a spectrum.
  • Database-per-tenant is the cleanest but expensive.
  • Schema-per-tenant balances cost & isolation for SaaS apps.
  • Row-based multi-tenancy is the most scalable but comes with risks.

If you’re building a new SaaS or multi-tenant system, your best bet is to start simple and migrate later if needed.


Multi-Tenancy at the Application Level

While most discussions on multi-tenancy focus on data (database, schema, or row-level isolation), you can also implement multi-tenancy at the application level by spinning up separate Go applications for each tenant.

This is less common but could make sense in specific scenarios.

1. Single Application, Multi-Tenant (Most Common)

  • One Go application serves all tenants.
  • Multi-tenancy is handled at the data level (separate DBs, schemas, or row-level filtering).
  • Requests are dynamically routed to the right tenant’s data.
  • Example: SaaS platform where all customers use app.example.com.

Simple to maintain
Efficient use of compute resources
More complex data access control
Scaling individual tenants is harder


2. Multi-App, One per Tenant (Less Common)

  • Each customer (tenant) gets their own Go application instance running separately.
  • This could be a dedicated EC2, Docker container, or Kubernetes pod per tenant.
  • Requests are routed to the appropriate application instance.
  • Example: High-security SaaS platforms where each customer gets a dedicated deployment.

Strong isolation between customers
Can customize app behavior per tenant
Easier to scale heavy-use customers independently
Expensive—each tenant needs its own compute resources
Harder to deploy updates across all tenants

You might see this in:

  • Enterprise SaaS with custom code per customer
  • Government & healthcare applications requiring strict data separation
  • High-value customers who demand dedicated infrastructure

3. Hybrid: Multi-App, but Shared Compute

  • Similar to one app per tenant, but instead of spinning up a full EC2 per tenant, you run multiple tenants' apps inside the same compute environment.
  • Could be multi-container (Docker, ECS, Kubernetes) where each container handles a different tenant.
  • Uses a reverse proxy to route requests to the right tenant’s instance.

More efficient than fully separate instances
Each tenant can have custom logic while sharing compute
Still allows scaling individual tenants separately
More DevOps complexity
Still needs careful resource management

Example:

  • A SaaS provider with a "premium" plan where big customers get their own isolated instance, but small customers use the shared multi-tenant version.

Which Approach is Best?

ScenarioBest Multi-Tenant Model
Small SaaS, shared computeSingle app, multi-tenant database
High-security enterprise SaaSDedicated app per tenant
SaaS with custom features per tenantHybrid approach: per-tenant app but shared infrastructure
Cost-sensitive SaaS scaling globallySingle app, multi-tenant DB with schema-per-tenant

But in most discussions, people refer to database multi-tenancy because it’s the most common and cost-effective approach. Spinning up a separate Go app per tenant is usually reserved for high-security or enterprise SaaS models.